package actors

import akka.actor.typed.{ActorRef, Behavior}
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import com.typesafe.config.{Config, ConfigFactory}

import java.util.{Date, UUID}
import scala.annotation.tailrec
import scala.concurrent.Future
import scala.util.{Failure, Success}

object Registry {

  import Messages._
  import slick.jdbc.PostgresProfile.api._
  import database.Model._
  import database.Connection

  val propsConfig: Config = ConfigFactory.load("propsConfiguration.properties")
  val domain: String = propsConfig.getString("serverHost")
  val port: String = propsConfig.getString("serverPort")

  object RegistryMaster {
    def apply(): Behavior[MasterProtocol] = Behaviors.setup { context: ActorContext[MasterProtocol] =>
      import context.executionContext

      val registrationFuture: Future[Seq[Registration]] = Connection.db.run(registrationTable.result)
      val devices: Map[String, ActorRef[DeviceProtocol]] = Map()

      registrationFuture.onComplete {
        case Success(registrations) =>
          @tailrec
          def initiatingDevices(registrations: Seq[Registration],
                                deviceMap: Map[String, ActorRef[DeviceProtocol]],
                                context: ActorContext[MasterProtocol]): Unit = {
            registrations match {
              case head :: tail =>
                val url = s"rtmp://$domain:$port/live/${head.id}"
                val camera: CameraDevice = CameraDevice(
                  id = head.id,
                  name = head.name,
                  streamURL = url,
                  onlineTime = (new Date).toString,
                  offlineTime = ""
                )
                initiatingDevices(tail, deviceMap + (head.id -> context.spawn(Device(camera), s"${context.self.path.name}-${head.name}")), context)
              case Nil =>
            }
          }
          initiatingDevices(registrations, devices, context)
        case Failure(exception) =>
          throw new RuntimeException(exception.getMessage)
      }

      active(devices)
    }

    def active(devices: Map[String, ActorRef[DeviceProtocol]]): Behavior[MasterProtocol] =
      Behaviors.receive[MasterProtocol] { (context: ActorContext[MasterProtocol], message: MasterProtocol) =>
        message match {
          case RegisterDevice(user, name, replyTo) =>
            context.log.info(s"[receptionist] Got an registration request from [$user] with name [$name]")
            val id: String = UUID.randomUUID().toString
            // construct stream URL
            val url = s"rtmp://$domain:$port/live/$id"
            // form new state
            val newCamera: CameraDevice = CameraDevice(
              id = id,
              name = name,
              streamURL = url,
              onlineTime = (new Date).toString,
              offlineTime = ""
            )
            val newDeviceRef: ActorRef[DeviceProtocol] = context.spawn(Device(newCamera), name)
            newDeviceRef ! DeviceRegisterTask(newCamera, replyTo)
            val updatedDevices: Map[String, ActorRef[DeviceProtocol]] = devices + (id -> newDeviceRef)
            context.log.info(s"[receptionist] Current registered devices: $updatedDevices")
            active(updatedDevices)
          case GetDeviceInfo(id, replyTo) =>
            context.log.info(s"[receptionist] Fetching information from device with id: $id")
            val deviceRef: ActorRef[DeviceProtocol] = devices(id)
            deviceRef ! DeviceGetInfoTask(replyTo)

            Behaviors.same
          case UpdateDeviceInfo(id, property, value, replyTo) =>
            context.log.info(s"[receptionist] Update information of device: $id")
            val deviceRef: ActorRef[DeviceProtocol] = devices(id)
            deviceRef ! DeviceUpdateInfoTask(property, value, replyTo)

            Behaviors.same
          case UnregisterDevice(id, replyTo) =>
            context.log.info(s"[receptionist] Un-registration request for device : $id")
            val deviceRef: ActorRef[DeviceProtocol] = devices(id)
            deviceRef ! DeviceUnregisterTask(id, replyTo)
            val updatedDevices: Map[String, ActorRef[DeviceProtocol]] = devices - id
            context.log.info(s"[receptionist] Current registered devices: $updatedDevices")
            active(updatedDevices)
          case ShowDevicesList(replyTo) =>
            context.log.info(s"[receptionist] Handling show devices list request.")
            context.log.info(s"[receptionist] Devices: ${devices.keys.mkString("[", ", ", "]")}")
            replyTo ! ShowDevicesResponse(devices)
            Behaviors.same
        }
      }
  }
}
